package httpsign

import (
	"errors"
	"fmt"

	"gitee.com/kobeyyf/apibox/middleware/httpsign/secret_provider"
	// "gitee.com/kobeyyf/apibox/response"
	// "github.com/gin-gonic/gin"
	"gitee.com/kobeyyf/apibox/handler"
	"github.com/spf13/viper"
)

var (
	providers = make(map[string]secret_provider.SecretProvider, 0)
)

func AddSecretProvider(name string, p secret_provider.SecretProvider) {
	providers[name] = p
}

type Authenticator struct {
	c *viper.Viper

	secretProvider secret_provider.SecretProvider

	authFunc handler.HandlerFunc

	whiteList map[string]map[string]struct{}
}

// http://127.0.0.1:9091/subapp/hello?Timestamp=2016-02-23T12:46:24Z&Format=XML&AccessKeyId=testid&Action=DescribeRegions&SignatureMethod=HMAC-SHA1&SignatureNonce=3ee8c1b8-83d3-44af-a94f-4e0ad82fd6cf&Version=2014-05-26&SignatureVersion=1.0&Signature=OLeaidS1JvxuMvnyHOwuJ%2BuX5qY%3D
func NewAuthenticator(c *viper.Viper) (auth *Authenticator, err error) {
	pName := c.GetString("apisign.secret_provider")

	p, has := providers[pName]
	if !has {
		return nil, errors.New("SecretProviderNotExist")
	}
	if err = p.Init(c); err != nil {
		return nil, fmt.Errorf("InitSecretProviderFailed: ", err.Error())
	}

	auth = &Authenticator{
		secretProvider: p,
		c:              c,
	}

	auth.loadWhiteList()

	auth.authFunc = func(c *handler.Context) *handler.Response {

		// 白名单匹配
		appWhitelist, has := auth.whiteList[c.Request.FormValue(handler.APPCODE)]
		if has {
			_, has = appWhitelist[c.Request.FormValue(handler.ACTION_NAME)]
			if has {
				return nil
			}
		}

		signature := c.Request.FormValue("Signature")
		signedString, apiErr := auth.getSignedStr(c)
		if apiErr != nil {
			return apiErr
		}

		if signature != signedString {
			return handler.NewErrorResponseWith400(handler.ErrorCode_IncompleteSignature, "IncompleteSignature")
		}
		return nil
	}

	return auth, nil
}

func (auth *Authenticator) Authenticated() handler.HandlerFunc {
	return auth.authFunc
}

func (auth *Authenticator) loadWhiteList() {
	whitelist := auth.c.GetStringMapStringSlice("apisign.whitelist")

	auth.whiteList = make(map[string]map[string]struct{}, len(whitelist))
	for app, actions := range whitelist {
		appWhiteList := make(map[string]struct{}, len(actions))
		auth.whiteList[app] = appWhiteList
		for _, action := range actions {
			appWhiteList[action] = struct{}{}
		}
	}
}

func (auth *Authenticator) getSignedStr(c *handler.Context) (string, *handler.Response) {
	if err := c.Parse(); err != nil {
		fmt.Println(err)
		return "", handler.NewErrorResponseWith400(handler.ErrorCode_UnknownError, "UnknownError")
	}

	form := c.Request.Form

	keyId := form.Get("AccessKeyId")
	secret, has := auth.secretProvider.GetSecret(keyId)
	if !has {
		return "", handler.NewErrorResponseWith400(handler.ErrorCode_InvalidParamater, "AccessKeyId")
	}

	return GetSignature(secret, c.Request.Method, form), nil
}

func (auth *Authenticator) DebugSignd(c *handler.Context) *handler.Response {
	str, apiErr := auth.getSignedStr(c)
	if apiErr != nil {
		return apiErr
	}

	return handler.NewSuccessResponse("Signature=" + str)
}
